superstrict

import BRL.LinkedList

import "../../framework/network/ServerPeer.bmx"
import "../../framework/network/netcodes.bmx"
import "../../lib/strutil/strutil.bmx"
import "../../lib/RabDB/RabDB.bmx"

type Master

	'// -------------------------------------------------------------------------------------------------------------
	'// Game info.
	'// -------------------------------------------------------------------------------------------------------------
	const GAME_NAME:string = "Fetherquest Online Master Server";
	const GAME_VERSION:string = "0.0.1 (Alpha)";
	const GAME_TITLE:string = GAME_NAME + " - " + GAME_VERSION;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Server settings.
	'// -------------------------------------------------------------------------------------------------------------
	const MASTER_PORT:short = 27530;
	const PVE_SERVER_PORT:short = 27540;
	const PVP_SERVER_PORT:short = 27550;
	const MAX_CONNECTIONS:short = 200;
	const ACCOUNT_DB:string = "db/accounts.rdl";
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Main function in this project. It's run through the master thread.
	'// -------------------------------------------------------------------------------------------------------------
	method run:object(data:object)
	
		m_serverPeer:ServerPeer = ServerPeer.create(MAX_CONNECTIONS, 30, MASTER_PORT);
		
		if (not m_serverPeer)
		
			print("ERROR: Master server peer could not be created.");
			print("Shutting down server.");
			return null;
		
		end if
		
		print("Loading database ~q" + ACCOUNT_DB + "~q.");
		
		local db:Database = RabDB.loadDatabase(ACCOUNT_DB);
		
		if (not db)
		
			print("ERROR: Failed to open database ~q" + ACCOUNT_DB + "~q.");
			print("Shutting down server.");
			return null;
		
		end if
		
		m_accountDatabase = db.getDatabase("accounts");
		
		if (not m_accountDatabase)
		
			print("ERROR: Could not find database ~qaccounts~q in " + ACCOUNT_DB + ".");
			print("Shutting down server.");
			return null;
		
		end if
		
		print("Master server was successfully launched.");
		
		while (not sMaster.m_stopEvent and not appTerminate())
		
			delay(10);
			managePackets();
		
		end while
		
		m_serverPeer.destroy();
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	method managePackets()
	
		local packet:ServerPacket = m_serverPeer.receive();
		
		if (packet)
		
			select (packet.m_msgType)
			
				case ID_NEW_INCOMING_CONNECTION
					if (packet.m_senderPort = PVE_SERVER_PORT)
					
						print("PVE Server " + packet.m_senderIndex + " connected from " + ..
							packet.m_senderAddr.ToString() + ":" + packet.m_senderPort + ".");
						m_PVERealmList.addLast(packet.m_senderAddr);
					
					else if (packet.m_senderPort = PVP_SERVER_PORT)
					
						print("PVP Server " + packet.m_senderIndex + " connected from " + ..
							packet.m_senderAddr.ToString() + ":" + packet.m_senderPort + ".");
						m_PVPRealmList.addLast(packet.m_senderAddr);
					
					else
					
						print("Client " + packet.m_senderIndex + " connected from " + ..
							packet.m_senderAddr.ToString() + ":" + packet.m_senderPort + ".");
					
					end if
				case ID_DISCONNECTION_NOTIFICATION
					if (packet.m_senderPort = PVE_SERVER_PORT)
					
						print("PVE Server " + packet.m_senderIndex + " disconnected.");
						m_PVERealmList.remove(packet.m_senderAddr);
					
					else if (packet.m_senderPort = PVP_SERVER_PORT)
					
						print("PVP Server " + packet.m_senderIndex + " disconnected.");
						m_PVPRealmList.remove(packet.m_senderAddr);
					
					else
					
						print("Client " + packet.m_senderIndex + " disconnected.");
					
					end if
				case ID_CONNECTION_LOST
					if (packet.m_senderPort = PVE_SERVER_PORT)
					
						print("PVE Server " + packet.m_senderIndex + " timed out.");
						m_PVERealmList.remove(packet.m_senderAddr);
					
					else if (packet.m_senderPort = PVP_SERVER_PORT)
					
						print("PVP Server " + packet.m_senderIndex + " timed out.");
						m_PVPRealmList.remove(packet.m_senderAddr);
					
					else
					
						print("Client " + packet.m_senderIndex + " timed out.");
					
					end if
				case ID_MODIFIED_PACKET
					print("WARNING: Received modified packet from " + ..
						packet.m_senderAddr.ToString() + ":" + packet.m_senderPort + "!");
					m_serverPeer.kick(packet.m_senderAddr);
					print("Connection kicked.");
				'// Program-specific packet.
				default
					local kind:string = strutil.word(0, packet.m_data);
					
					'// See what kind of packet we have received.
					select (kind)
					
						'// Command packets.
						case netcodes.ID_COMMAND
						'// Info packets.
						case netcodes.ID_INFO
							local infoType:string = strutil.word(1, packet.m_data);
							local infoData:string;
							
							for local i:int = 2 until strutil.words(packet.m_data)
							
								if (infoData) ..
									infoData = infoData + " " + strutil.word(i, packet.m_data); ..
								else ..
									infoData = strutil.word(i, packet.m_data);
							
							next
							
							select infoType
							
								case netcodes.INFO_SERVER_NAME
									'// TBW.
							
							end select
						'// Query packets.
						case netcodes.ID_QUERY
							local query:string = strutil.word(1, packet.m_data);
							local args:string;
							
							for local i:int = 2 until strutil.words(packet.m_data)
							
								if (args) ..
									args = args + " " + strutil.word(i, packet.m_data); ..
								else ..
									args = strutil.word(i, packet.m_data);
							
							next
							
							'// Handle the query.
							select (query)
							
								'// Handle a query to login to an account.
								case netcodes.QUERY_LOGIN_TO_ACCOUNT
									local passwordCorrect:byte = false;
									local banned:byte = false;
									local un:string = arg(args, 1);
									local up:string = arg(args, 2);
									local accountsTable:Table = m_accountDatabase.getTable("accounts");
									local account:Row = accountsTable.getRow(un);
									
									'// If 'account' is not null, we know it is a valid row in the table "accounts", which means the
									'// account name is valid.
									if (account)
									
										'// See if the password matches as well.
										if (up = accountsTable.getString(un, "password"))
										
											passwordCorrect = true;
											banned = accountsTable.getBool(un, "banned");
										
										end if
										
										'// If the password matches, go on.
										if (passwordCorrect)
										
											if (not banned)
											
												print("Client " + packet.m_senderIndex + " logged into account ~q" + un + "~q.");
												
												sendMessageToPeer(netcodes.ID_ANSWER + " " + netcodes.ANSWER_LOGIN_SUCCESSFUL, 0, ..
													packet.m_senderAddr);
											
											else
											
												print("Client " + packet.m_senderIndex + " is banned and will not be allowed in.");
												
												sendMessageToPeer(netcodes.ID_ANSWER + " " + netcodes.ANSWER_ACCOUNT_BANNED, 0, ..
													packet.m_senderAddr);
											
											end if
										
										'// If the password does not match, tell the client.
										else
										
											print("Client " + packet.m_senderIndex + " entered the wrong password.");
											
											sendMessageToPeer(netcodes.ID_ANSWER + " " + netcodes.ANSWER_INVALID_ACCOUNTPASS, 0, ..
												packet.m_senderAddr);
										
										end if
									
									'// If the username does not exist, tell the client.
									else
									
										print("Client " + packet.m_senderIndex + " tried to log into an non-existing account.");
										
										sendMessageToPeer(netcodes.ID_ANSWER + " " + netcodes.ANSWER_INVALID_ACCOUNTNAME, 0, ..
											packet.m_senderAddr);
									
									end if
								'// Handle a query to receive a PVE realm list.
								case netcodes.QUERY_PVE_REALM_LIST
									print("Sending PVE Realmlist to " + packet.m_senderAddr.ToString() + ".");
									
									sendMessageToPeer(netcodes.ID_LISTSTART + " " + netcodes.LIST_PVE_REALM_LIST, 0, ..
										packet.m_senderAddr);
									
									for local s:TRKSystemAddress  = eachin m_PVERealmList
									
										sendMessageToPeer(netcodes.ID_LISTDATA + " " + netcodes.LIST_PVE_REALM_LIST + " " + ..
											s.ToString(), 0, packet.m_senderAddr);
									
									next
									
									sendMessageToPeer(netcodes.ID_LISTEND + " " + netcodes.LIST_PVE_REALM_LIST, 0, ..
										packet.m_senderAddr);
								'// Handle a query to receive a PVP realm list.
								case netcodes.QUERY_PVP_REALM_LIST
									print("Sending PVP Realmlist to " + packet.m_senderAddr.ToString() + ".");
									
									sendMessageToPeer(netcodes.ID_LISTSTART + " " + netcodes.LIST_PVP_REALM_LIST, 0, ..
										packet.m_senderAddr);
									
									for local s:TRKSystemAddress  = eachin m_PVPRealmList
									
										sendMessageToPeer(netcodes.ID_LISTDATA + " " + netcodes.LIST_PVP_REALM_LIST + " " + ..
											s.ToString(), 0, packet.m_senderAddr);
									
									next
									
									sendMessageToPeer(netcodes.ID_LISTEND + " " + netcodes.LIST_PVP_REALM_LIST, 0, ..
										packet.m_senderAddr);
							
							end select
						'// Unknown packet, tell the user.
						default
							print("Unknown packet received from " + ..
								packet.m_senderAddr.ToString() + ":" + packet.m_senderPort + ..
								" containing the data ~q" + packet.m_data + "~q.");
					
					end select
			
			end select
		
		end if
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	method sendMessageToPeer(msg:string, packetChannel:int, targetAddr:TRKSystemAddress)
	
		local message:byte ptr = msg.toCString();
		m_serverPeer.m_peer.Send(message, msg.length + 1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, targetAddr, false);
		memFree(message);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Extract a specific argument from a message.
	'// -------------------------------------------------------------------------------------------------------------
	method arg:string(args:string, a:int)
	
		if (a < 1) ..
			return null;
		
		return strutil.word(a - 1, args);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	field m_stopEvent:byte = false;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	field m_serverPeer:ServerPeer = null;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	field m_accountDatabase:Database = null;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// 
	'// -------------------------------------------------------------------------------------------------------------
	field m_PVERealmList:TList = new TList;
	field m_PVPRealmList:TList = new TList;

end type

global sMaster:Master = new Master;
